/*
 * Created on 2007
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.XSQS.storage.manager;

import java.util.ArrayList;

import com.XSQS.storage.util.IORequest;
import com.XSQS.storage.util.Page;

/**
 * @author Jofee.Chan
 */
public class BufferManager {
    private static BufferManager bm;
    private DataFileManager dfm;
    private IOChannel ioc;
    // 
    private static final int BUF_POOL_SIZE = 256;
    private static final int BASE_NUM = 8;
    //private static int[] LRUinfo;
    //private static int head = 0, rear = 255, curPos = 0;
    private static ArrayList<BlockInfo>[] hash;
    private static LRU lru;
    private static BufBlk[] bufPool;
    
    /** Creates a new instance of BufferManager */
    private BufferManager() {
        dfm = DataFileManager.getInstance();
        ioc = IOChannel.getInstance();
        //LRUinfo = new int[BUF_POOL_SIZE];
        hash = new ArrayList[BASE_NUM];
        lru = new LRU();
        bufPool = new BufBlk[BUF_POOL_SIZE];
        for (int i = 0; i < BUF_POOL_SIZE; i++) {
            //blkLRU[i] = i + 1;
            bufPool[i] = new BufBlk();
        }
        for (int i = 0; i < BASE_NUM; i++) {
            hash[i] = new ArrayList<BlockInfo>();
        }
    }
    
    public synchronized static BufferManager getInstance(){
        if(bm == null){
            bm = new BufferManager();
        }
        return bm;
    }
    
    protected int readDocInfoPage2Buf(int lpno){
        return readPage2Buf(Page.DOCINFOPAGE, lpno, false);
    }
    
    protected int readDataPage2Buf(int lpno){
        return readPage2Buf(Page.DATAPAGE, lpno, false);
    }
    
    protected int readStringPage2Buf(int lpno){
        return readPage2Buf(Page.STRINGPAGE, lpno, false);
    }
    
    protected int prepareNewDocInfoPage(int lpno){
        return readPage2Buf(Page.DOCINFOPAGE, lpno, true);
    }
    
    protected int prepareNewDataPage(int lpno){
        return readPage2Buf(Page.DATAPAGE, lpno, true);
    }
    
    protected int prepareNewStringPage(int lpno){
        return readPage2Buf(Page.STRINGPAGE, lpno, true);
    }
    
  
    protected int readPage2Buf(short type, int lpno, boolean newDoc) {
        int no = 0;
        //for (int i = 0; i < 4; i++) {
        if(lpno == -1){
            System.err.println(type + "Errooooooooooooooooooooooor!!");
        }
        no = checkPage(type, lpno);
        if (no == -1) {
            no = getNewBuf();
            Page page = null;
            if(newDoc){
                //page = new 
            }else{
                IORequest ior = new IORequest(type, lpno);
                int reqID = ioc.putRequest(ior);
                page = ioc.getResponse(reqID).buildPage();
            }                       
            if (page != null || newDoc) {
                synchronized(this){
                    bufPool[no].setType(type);
                    bufPool[no].setLpno(lpno);
                    bufPool[no].setData(page);
                    //BNo_SId_LPNo temp = new BNo_SId_LPNo(no, setId, lpno + i);
                    hash[lpno % BASE_NUM].add(new BlockInfo(no, type, lpno));
                }
            } else {
                return -1;
            }
        }
        //}
        return no;
    }
    
    
    
    /**
     *
     */
   /* protected int read4Pages2Buf(short type, int lpno) {
        int no = 0, bufNo = 0;
        for (int i = 0; i < 4; i++) {
            no = checkPage(type, lpno);
            if (no == -1) {
                no = getNewBuf();
                Page page = dfm.readPage(type, lpno, true);
                if (page != null) {
                    bufPool[no].setType(type);
                    bufPool[no].setLpno(lpno);
                    bufPool[no].setData(page);
                    //BNo_SId_LPNo temp = new BNo_SId_LPNo(no, setId, lpno + i);
                    hash[lpno % BASE_NUM].add(new BlockInfo(no, type, lpno));
                } else {
                    return -1;
                }
            }
            if (i == 0) {
                bufNo = no;
            }
        }
        return bufNo;
    }*/
    
    //public int freeBuf(int bufNo) {
    // return bufNo;
    //}
    
    /* */
    synchronized int getNewBuf() {
        int no;
        boolean isFull;
        
        no = lru.getHead();
        isFull = lru.isFull();
        
        if (isFull) {
            ArrayList<BlockInfo> al = hash[bufPool[no].getLpno() % BASE_NUM];
            for(int i = 0; i < al.size(); i++){
                if(al.get(i).contains(bufPool[no].getType(), bufPool[no].getLpno())){
                    al.remove(i);
                    break;
                }
            }
            if (bufPool[no].isUpdated()) {
                flushBuf(no);
            }
        }
        lru.moveHead();
        return no;
    }
    
    protected Page readBuf(int bufNo) {
        return bufPool[bufNo].getData();
    }
    
    protected void writeBuf(int bufNo, Page page){
        synchronized(bufPool[bufNo]){
            bufPool[bufNo].setData(page);
            bufPool[bufNo].setIsUpdated(true);
        }
    }
    
    // 
    void setIsUpdated(int bufNo, boolean isUpdated) {
        synchronized(bufPool[bufNo]){
            bufPool[bufNo].setIsUpdated(isUpdated);
        }
    }
    
    /**
     * 
     */
    protected void flushBuf(int bufNo) {
        synchronized(bufPool[bufNo]){
            if (bufPool[bufNo].isUpdated()) {
                IORequest ior = new IORequest(bufPool[bufNo].getType(), bufPool[bufNo].getLpno(),
                        bufPool[bufNo].getData().toByteArray());
                int reqID = ioc.putRequest(ior);
                if(reqID > 0){
                    bufPool[bufNo].setIsUpdated(false);
                }else{
                    System.err.println("Error flush Buffer. No: " + bufNo + "\nWrong reqID: " + reqID);
                }
            }
        }
    }
    
   
    protected void flushAll() {
        for (int i = 0; i < BUF_POOL_SIZE; i++) {
            if (bufPool[i].isUpdated()) {
                flushBuf(i);
            }
        }
        System.out.println("Buffer Flushed Successfully.");
    }
    
    
    protected synchronized int checkPage(short type, int lpno) {
        int pos = lpno % BASE_NUM;
        for (int i = 0; i < hash[pos].size(); i++) {
            BlockInfo temp = hash[pos].get(i);
            if (temp.contains(type, lpno)) {
               
                int bufNo = temp.getBufNo();
                ////////////////////////////////////////////////////
                lru.access(bufNo);
                return bufNo;
            }
        }
        
        return -1;
    }
    
//
    private class BufBlk {
        
        private short type = 0;
        private int lpno = 0;
        private short bufState = 0;
        private boolean isUpdated = false;
        private Page data; //
        private boolean isReady;
        short getType() {
            return type;
        }
        
        int getLpno() {
            return lpno;
        }
        
        short getBufState() {
            return bufState;
        }
        
        boolean isUpdated() {
            return isUpdated;
        }
        
        Page getData() {
            return data;
        }
        
        void setData(Page page){
            this.data = page;
        }
        
        void setType(short type) {
            this.type = type;
        }
        
        void setLpno(int lpno) {
            this.lpno = lpno;
        }
        
        void setBufState(short bufState) {
            this.bufState = bufState;
        }
        
        void setIsUpdated(boolean isUpdated) {
            this.isUpdated = isUpdated;
        }
    }
    
    
    private class BlockInfo {
        private int bNo = -1;
        private short t = -1;
        private int lpNo = -1;
        BlockInfo(int bufNo, short type, int lpno) {
            bNo = bufNo;
            t = type;
            lpNo = lpno;
        }
        
        int getBufNo() {
            return bNo;
        }
        
        short getType() {
            return t;
        }
        
        int getLpno() {
            return lpNo;
        }
        
        boolean contains(short type, int lpno) {
            if (t == type && lpNo == lpno) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    private class LRU{
        private int[] LRUinfo;
        private int head = 0, curPos = 1; // rear = 255
        
        public LRU(){
            LRUinfo = new int[BUF_POOL_SIZE];
        }
        
        public int getHead(){
            if(LRUinfo[head] == 0){
                LRUinfo[head] = curPos++;
            }
            return LRUinfo[head] - 1;  //
        }
//Ori       
        public boolean isFull(){
            return curPos > BUF_POOL_SIZE;
        }
//should be updated
//        public boolean isFull(){
//        	return curPos > BUF_POOL_SIZE-1;
//        }
        
        public void moveHead(){
            head = (head + 1) % BUF_POOL_SIZE;
            // rear = (rear + 1) % BUF_POOL_SIZE;
        }
        
        public void access(int bufNo){
            for (int j = 0; j < BUF_POOL_SIZE; j++) {
                int p = (head + j) % BUF_POOL_SIZE;
                //int t = head;
                if (LRUinfo[p] == bufNo + 1) {
                    int[] tempLRU = new int[BUF_POOL_SIZE];
                    
                    for (int k = 0; k < BUF_POOL_SIZE - 1; k++) {
                        if (head % BUF_POOL_SIZE != p) {
                            tempLRU[k] = LRUinfo[head % BUF_POOL_SIZE];
                        } else {
                            k--;
                        }
                        head++;
                    }
                    tempLRU[BUF_POOL_SIZE - 1] = LRUinfo[p];
                    System.arraycopy(tempLRU, 0, LRUinfo, 0, BUF_POOL_SIZE);
                    head = 0;
                    //rear = BUF_POOL_SIZE - 1;
                    return;
                }
            }
        }
    }
}
